global loader                          ; Make entry point visible to linker.
extern main                            ; _main is defined elsewhere
 
; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0             ; align loaded modules on page boundaries
MEMINFO     equ  1<<1             ; provide memory map
FLAGS       equ  MODULEALIGN | MEMINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002     ; 'magic number' lets bootloader find the header
CHECKSUM    equ -(MAGIC + FLAGS)  ; checksum required
 
; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000                  ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)  ; Page directory index of kernel's 4MB PTE.

section .data
align 0x1000
BootPageDirectory:
    ; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
    ; All bits are clear except the following:
    ; bit 7: PS The kernel page is 4MB.
    ; bit 1: RW The kernel page is read/write.
    ; bit 0: P  The kernel page is present.
    ; This entry must be here -- otherwise the kernel will crash immediately after paging is
    ; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
	dd (BootPageTable + 3 - KERNEL_VIRTUAL_BASE)
	times (KERNEL_PAGE_NUMBER - 1) dd 0                 ; Pages before kernel space.
KernelPage:
	dd (BootPageTable + 3 - KERNEL_VIRTUAL_BASE)
	times (1024 - KERNEL_PAGE_NUMBER - 2) dd 0  ; Pages after the kernel image.
	dd (BootPageDirectory + 3 - KERNEL_VIRTUAL_BASE)
BootPageTable:
	times (1024) dd 0
EndPageDirectory:

	magic dd 0
	multiboot dd 0
 
section .text
align 4
MultiBootHeader:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000
 
loader:

	mov [magic - KERNEL_VIRTUAL_BASE], eax
	mov [multiboot - KERNEL_VIRTUAL_BASE], ebx

	xor ebx, ebx
	add ebx, 3

	mov ecx, (BootPageTable - KERNEL_VIRTUAL_BASE)

.1:
	mov [ecx], ebx
	add ecx, 4
	add ebx, 0x1000
	cmp ecx, (EndPageDirectory - KERNEL_VIRTUAL_BASE)
	jne .1

    ; NOTE: Until paging is set up, the code must be position-independent and use physical
    ; addresses, not virtual ones!
	mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
	mov cr3, ecx                                        ; Load Page Directory Base Register.

	mov ecx, cr0
	or ecx, 0x80000000                          ; Set PG bit in CR0 to enable paging.
	mov cr0, ecx
 
    ; Start fetching instructions in kernel space.
	lea ecx, [StartInHigherHalf]
	jmp ecx                                                     ; NOTE: Must be absolute jump!
 
StartInHigherHalf:
    ; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
    ; anymore.
	mov dword [BootPageDirectory], 0
	invlpg [0]
 
    ; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
    ; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
    ; position-independent code or funny business with virtual-to-physical address translation
    ; should be necessary. We now have a higher-half kernel.
	mov esp, stack+STACKSIZE           ; set up the stack
	mov eax, [magic]
	push eax                           ; pass Multiboot magic number
 
    ; pass Multiboot info structure -- WARNING: This is a physical address and may not be
    ; in the first 4MB!
	mov ebx, [multiboot]
	push ebx
 
	call main                  ; call kernel proper
	cli
	hlt                          ; halt machine should kernel return
 
section .bss
align 32
stack:
   resb STACKSIZE      ; reserve 16k stack on a quadword boundary
